# -*- coding: utf-8 -*-
import sys
import threading
import time
import traceback

import numpy as np
import pandas as pd
import mplfinance as mpf
import matplotlib.pyplot as plt
from binance.client import Client
from matplotlib import gridspec
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk
from tkinter import ttk, messagebox
import json
import os

import util
from BiAn import get_binance_data
from tdx import Zhibiao


class MplFinanceTkinterApp:

    auto_refresh = False
    thread_running = False;
    lock = threading.RLock()

    # config_file = 'symbol_list_BIG.json'
    pass_symbol = ['BTCUSDT']


    zlqx = 0
    def __init__(self, root):
        self.root = root
        self.root.title("K线图分析系统")
        self.root.geometry("1280x800")
        self.button_text = tk.StringVar(value="自动刷新")  # 创建文字变量

        self.file_list = ["symbol_list_BIG.json", "symbol_list_NORMAL_BIG.json", "symbol_list_NORMAL.json", "symbol_list.json"]  # 预设文件列表
        self.current_file_var = tk.StringVar(value=self.file_list[0])  # 当前选中文件

        # 初始化控制变量
        self.symbol_var = tk.StringVar(value="BTCUSDT")
        self.interval_var = tk.StringVar(value="4h")
        self.limit_var = tk.StringVar(value="200")
        self.new_symbol_var = tk.StringVar()

        # 加载币种列表
        self.symbol_list = self.load_symbol_list()

        # 创建主界面
        self.main_frame = ttk.Frame(root)
        self.main_frame.pack(expand=True, fill=tk.BOTH, padx=10, pady=10)

        # 图表变量
        self.fig = None
        self.axes = None
        self.canvas = None
        self.hover = None

        self.long_profit_pct = 0  # 做多累计盈利点数
        self.short_profit_pct = 0  # 做空累计盈利点数
        self.profit_label = None  # 盈利显示标签

        # 构建界面
        self.setup_ui()
        self.refresh_data()

    def load_symbol_list(self):
        """加载持久化的币种列表"""
        config_file = self.current_file_var.get()  # 使用当前选择的文件名

        default_symbols = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "ADAUSDT"]
        # 获取当前工作目录
        current_directory = os.getcwd()

        # 打印当前工作目录
        print("当前工作目录:", current_directory)
        try:
            if os.path.exists(config_file):
                with open(config_file, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                    loaded_symbols = data.get('symbols', [])
                    # 合并默认币种和加载的币种，并去重排序
                    return list(loaded_symbols)
        except Exception as e:
            print(f"加载币种列表出错: {e}")

        return default_symbols

    def save_symbol_list(self):
        """保存币种列表到文件"""
        config_file = self.current_file_var.get()  # 使用当前选择的文件名
        try:
            with open(config_file, 'w', encoding='utf-8') as f:
                json.dump({'symbols': self.symbol_list}, f, ensure_ascii=False, indent=2)
        except Exception as e:
            print(f"保存币种列表出错: {e}")

    def setup_ui(self):
        """构建用户界面"""
        # 控制面板
        control_frame = ttk.Frame(self.main_frame)
        control_frame.pack(fill=tk.X, padx=5, pady=5)

        # 刷新按钮
        ttk.Button(control_frame, textvariable=self.button_text, command=self.auto_refresh_data).pack(side=tk.LEFT, padx=5)

        # 周期选择
        ttk.Label(control_frame, text="周期:").pack(side=tk.LEFT, padx=(10, 0))
        ttk.OptionMenu(
            control_frame, self.interval_var, "4h",
            "1m", "5m", "15m", "30m", "1h", "4h", "1d",
            command=self.change_interval
        ).pack(side=tk.LEFT, padx=5)

        # 币种选择
        ttk.Label(control_frame, text="币种:").pack(side=tk.LEFT, padx=(10, 0))
        svalues = [f"{i} {symbol}" for i, symbol in enumerate(self.symbol_list, start=1)]
        self.symbol_menu = ttk.OptionMenu(
            control_frame, self.symbol_var, "1 " + self.symbol_list[0],
            *svalues, command=self.change_symbol
        )
        self.symbol_menu.pack(side=tk.LEFT, padx=5)

        ttk.Label(control_frame, text="配置文件:").pack(side=tk.LEFT, padx=(10, 0))
        ttk.OptionMenu(
            control_frame,
            self.current_file_var,
            self.file_list[0],
            *self.file_list,
            command=self.change_config_file
        ).pack(side=tk.LEFT, padx=5)

        # 新增币种
        ttk.Label(control_frame, text="新增:").pack(side=tk.LEFT, padx=(10, 0))
        ttk.Entry(control_frame, textvariable=self.new_symbol_var, width=10).pack(side=tk.LEFT)
        ttk.Button(control_frame, text="添加", command=self.add_new_symbol).pack(side=tk.LEFT, padx=5)

        # K线数量
        ttk.Label(control_frame, text="数量:").pack(side=tk.LEFT, padx=(10, 0))
        ttk.OptionMenu(
            control_frame, self.limit_var, "200",
            "100", "200", "300", "400", "500", "600",
            command=self.change_limit
        ).pack(side=tk.LEFT)

        # 在控制面板中添加盈利显示
        self.profit_label = ttk.Label(control_frame,
                                      text=f"盈利点数 | 做多: {self.long_profit_pct:.4f} | 做空: {self.short_profit_pct:.4f}")
        self.profit_label.pack(side=tk.LEFT, padx=(20, 0))

    def change_config_file(self, event=None):
        """配置文件变更处理"""
        self.config_file = self.current_file_var.get()  # 更新当前配置文件
        self.symbol_list = self.load_symbol_list()  # 重新加载币种列表
        self.update_symbol_menu()  # 更新币种下拉菜单
        self.refresh_data()  # 刷新数据

    def create_professional_chart(self):
        """创建专业级K线图表（修复成交量显示）"""
        # 清除旧图表
        if self.canvas:
            self.canvas.get_tk_widget().destroy()

        # 专业颜色配置
        mc = mpf.make_marketcolors(
            up='#2b7c36',  # 深绿色
            down='#d62728',  # 深红色
            volume={'up': '#2b7c36', 'down': '#d62728'},
            edge={'up': '#2b7c36', 'down': '#d62728'},
            wick={'up': '#2b7c36', 'down': '#d62728'},
            ohlc='i'
        )

        # 使用有效的样式名称
        style = mpf.make_mpf_style(
            base_mpl_style='default',  # 改为有效的默认样式
            marketcolors=mc,
            gridstyle=':',
            rc={
                'font.family': 'Microsoft YaHei',
                'axes.labelcolor': '#555555',
                'axes.grid.axis': 'y'
            }
        )

        # rsi = calculate_rsi(self.data['close'])

        # 创建附加绘图
        apds = [
            mpf.make_addplot(self.data['boll_upper'], panel=0, color='#1f77b4', alpha=0.8),
            mpf.make_addplot(self.data['boll_middle'], panel=0, color='#ff7f0e', alpha=0.8),
            mpf.make_addplot(self.data['boll_lower'], panel=0, color='#1f77b4', alpha=0.8),
            mpf.make_addplot(self.fxxs, panel=2, color='#9467bd', ylabel='指标', ylim=(0, 100)),
            # mpf.make_addplot(rsi, panel=2, color='#0000bd', ylabel='指标', ylim=(0, 100)),
            # 新增B/S信号标记
            mpf.make_addplot(
                self.data['buy_marker'],
                type='scatter',
                marker=r'$\mathrm{B}$',  # 使用LaTeX格式的B
                markersize=120,
                color='green',
                panel=0
            ),
            mpf.make_addplot(
                self.data['sell_marker'],
                type='scatter',
                marker=r'$\mathrm{S}$',  # 使用LaTeX格式的S
                markersize=120,
                color='red',
                panel=0
            ),
            mpf.make_addplot([80] * len(self.fxxs), panel=2, color='#d62728', linestyle='--', ylim=(0, 100)),
            mpf.make_addplot([20] * len(self.fxxs), panel=2, color='#2ca02c', linestyle='--', ylim=(0, 100))

        ]

        if self.data['buy_marker'].isna().all():
            self.data.loc[self.data.index[0], 'buy_marker'] = self.data['low'].iloc[0] * 0.98  # 在第一个位置添加临时标记

        if self.data['sell_marker'].isna().all():
            self.data.loc[self.data.index[0], 'sell_marker'] = self.data['high'].iloc[0] * 1.02

        # 专业图表配置
        fig, axes = mpf.plot(
            self.data,
            type='candle',
            style=style,
            addplot=apds,
            figsize=(12, 8),
            # title=f'\n{self.symbol_var.get()} {self.interval_var.get()}',
            ylabel='价格',
            volume=True,
            volume_panel=1,
            panel_ratios=(6, 2, 2),
            tight_layout=True,
            returnfig=True,
            scale_width_adjustment=dict(
                candle=0.9,
                volume=0.7
            ),
            update_width_config={
                'candle_linewidth': 0.8,
                'candle_width': 0.6,
                'volume_linewidth': 0.8,
                'volume_width': 0.7
            }
        )

        # 设置Y轴范围
        axes[0].set_ylim(
            self.data['low'].min() * 0.995,
            self.data['high'].max() * 1.005
        )

        # 创建画布
        self.fig, self.axes = fig, axes
        self.canvas = FigureCanvasTkAgg(fig, master=self.main_frame)
        self.canvas.draw()
        self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)

        # 连接交互事件
        self.connect_hover_event()

        self.update_profit_display()

    def calculate_indicators(self):
        """计算技术指标"""
        # 计算布林带
        rolling_mean = self.data['close'].rolling(window=20).mean()
        rolling_std = self.data['close'].rolling(window=20).std()
        self.data['boll_upper'] = rolling_mean + (rolling_std * 2)
        self.data['boll_middle'] = rolling_mean
        self.data['boll_lower'] = rolling_mean - (rolling_std * 2)

        # 计算自定义指标
        self.fxxs, self.buy = Zhibiao.ths_chaodi(self.data)

        zlqx = Zhibiao.zlqx(self.data)
        self.zlqx = zlqx[-1]['zlqx']

        self.data['buy_marker'] = np.nan
        self.data['sell_marker'] = np.nan

        buy = -1
        buy_time = ""
        trade_records = []

        for index, i in enumerate(self.fxxs):
            if pd.isna(i):
                continue

            float_value = float(i)
            if float_value >= 80:  # 卖出信号
                if buy != -1:
                    time_str = self.data.index[index].strftime('%Y-%m-%d %H:%M')
                    sell_price = float(self.data['close'][index])
                    zdf = (sell_price - buy) / buy
                    trade_records.append({
                        "buy": buy,
                        "sell": sell_price,
                        "zdf": zdf,
                        "buy_time": buy_time,
                        "sell_time": time_str
                    })
                    # 记录卖出标记（在最高价上方2%处）
                    self.data.at[self.data.index[index], 'sell_marker'] = self.data['high'][index] * 1.01
                    buy = -1
            elif float_value <= 20:  # 买入信号
                if buy == -1:
                    buy_time = self.data.index[index].strftime('%Y-%m-%d %H:%M')
                    buy = float(self.data['close'][index])
                    # 记录买入标记（在最低价下方2%处）
                    self.data.at[self.data.index[index], 'buy_marker'] = self.data['low'][index] * 0.99

        print("交易记录:", trade_records)

        # 初始化盈利数据
        self.long_profit_pct = 0.0  # 做多累计百分比
        self.short_profit_pct = 0.0  # 做空累计百分比

        # 直接利用已经标记的买卖信号
        buy_signals = self.data[self.data['buy_marker'].notna()].index
        sell_signals = self.data[self.data['sell_marker'].notna()].index

        # 计算做多盈利（每个S与上一个B的涨跌幅总和）
        for i, sell_time in enumerate(sell_signals):
            # 找到这个卖出信号之前的所有买入信号
            prev_buys = buy_signals[buy_signals < sell_time]

            if len(prev_buys) > 0:
                # 取最近的一个买入信号
                last_buy_time = prev_buys[-1]

                # 获取价格
                buy_price = self.data.loc[last_buy_time, 'close']
                sell_price = self.data.loc[sell_time, 'close']

                # 计算涨跌幅（百分比）
                pct_change = (sell_price - buy_price) / buy_price * 100
                self.long_profit_pct += pct_change
                print(f"{last_buy_time} 到 {sell_time} 的做多盈利: {pct_change:.2f}%,总盈利{self.long_profit_pct:.2f}")

        # 计算做空盈利（每个B与上一个S的涨跌幅总和）
        for i, buy_time in enumerate(buy_signals):
            # 找到这个买入信号之前的所有卖出信号
            prev_sells = sell_signals[sell_signals < buy_time]

            if len(prev_sells) > 0:
                # 取最近的一个卖出信号
                last_sell_time = prev_sells[-1]

                # 获取价格
                sell_price = self.data.loc[last_sell_time, 'close']
                buy_price = self.data.loc[buy_time, 'close']

                # 计算涨跌幅（百分比）
                pct_change = (sell_price - buy_price) / sell_price * 100
                self.short_profit_pct += pct_change

        ths = float(self.fxxs[-1])
        if ths <= 20:
            util.play_voidce("达到买入位置")
        elif ths >= 80:
            util.play_voidce("达到卖出位置")

        close = float(self.data['close'][-1])
        open = float(self.data['open'][-1])
        if self.interval_var.get() == "1m":
            if (close - open)/open > 0.02:
                util.play_voidce("瞬间涨幅达到2%以上")
            elif (close - open)/open < -0.02:
                util.play_voidce("瞬间跌幅达到2%以上")




    def update_profit_display(self):
        """更新盈利点数显示"""
        if hasattr(self, 'profit_label'):
            self.profit_label.config(
                text=f"盈利点数 | 做多: {self.long_profit_pct:.2f} | 做空: {self.short_profit_pct:.2f} | 清洗指标：{self.zlqx}")
        print(f"Debug - 做多盈利: {self.long_profit_pct:.2f}, 做空盈利: {self.short_profit_pct:.2f}")  # 调试输出

    # def refresh_data(self):
    #     """刷新数据"""
    #     try:
    #         self.data = self.get_binance_data(
    #             symbol=self.symbol_var.get(),
    #             interval=self.interval_var.get()
    #         )
    #         self.create_professional_chart()
    #     except Exception as e:
    #         messagebox.showerror("错误", f"获取数据失败: {str(e)}")

    def refresh_data(self):
        # 创建并启动线程
        thread = threading.Thread(target=self.worker)
        thread.start()

    def auto_refresh_data(self):
        with self.lock:
            if not self.auto_refresh and not self.thread_running:
                self.auto_refresh = True
                thread = threading.Thread(target=self.loop_refresh_data)
                thread.start()
                thread = threading.Thread(target=self.monitor_all_symbols)
                thread.start()
                self.button_text.set("正在自动")
            else:
                self.auto_refresh = False
                self.button_text.set("自动刷新")

    def loop_refresh_data(self):
        print("self.auto_refresh = " + str(self.auto_refresh))
        while self.auto_refresh:
            print("loop_refresh_data")
            self.thread_running = True
            self.worker()
            time.sleep(2)
        self.thread_running = False

    def worker(self):
        try:
            # 在子线程中获取数据
            n_symble = self.symbol_var.get().split(" ")[1]
            data = get_binance_data(
                self.limit_var.get(),
                symbol=n_symble,
                interval=self.interval_var.get()
            )
            # 获取 Tkinter 根窗口（假设你的类里有一个 root 属性）
            root = self.root  # 或者 self.master（取决于你的 GUI 结构）

            data['buy_marker'] = np.nan
            data['sell_marker'] = np.nan
            self.data = data
            self.calculate_indicators()

            # 回到主线程更新数据和创建图表
            root.after(0, lambda: self.update_chart(data))
        except Exception as e:
            # 错误处理也要回到主线程
            a = e
            # self.root.after(0, lambda e=a: messagebox.showerror("错误", f"获取数据失败: {str(e)}"))

    def update_chart(self, data):
        """更新图表数据"""
        # self.data = data
        # self.calculate_indicators()

        # 更新UI显示
        self.update_profit_display()

        # 如果是第一次创建图表
        if not self.canvas:
            self.create_professional_chart()
            return

        try:
            # 清除所有轴的内容但保留轴对象
            for ax in self.axes:
                ax.clear()

            # 专业颜色配置
            mc = mpf.make_marketcolors(
                up='#2b7c36',  # 深绿色
                down='#d62728',  # 深红色
                volume={'up': '#2b7c36', 'down': '#d62728'},
                edge={'up': '#2b7c36', 'down': '#d62728'},
                wick={'up': '#2b7c36', 'down': '#d62728'},
                ohlc='i'
            )

            style = mpf.make_mpf_style(
                base_mpl_style='default',
                marketcolors=mc,
                gridstyle=':',
                rc={
                    'font.family': 'Microsoft YaHei',
                    'axes.labelcolor': '#555555',
                    'axes.grid.axis': 'y'
                }
            )
            if 'buy_marker' not in self.data.columns:
                self.data['buy_marker'] = np.nan
            if 'sell_marker' not in self.data.columns:
                self.data['sell_marker'] = np.nan
            # 准备附加绘图 - 现在只使用前两个面板(K线和成交量)
            apds = [
                mpf.make_addplot(self.data['boll_upper'], ax=self.axes[0], color='#1f77b4', alpha=0.8),
                mpf.make_addplot(self.data['boll_middle'], ax=self.axes[0], color='#ff7f0e', alpha=0.8),
                mpf.make_addplot(self.data['boll_lower'], ax=self.axes[0], color='#1f77b4', alpha=0.8),
                mpf.make_addplot(
                    self.data['buy_marker'],
                    type='scatter',
                    marker=r'$\mathrm{B}$',
                    markersize=120,
                    color='green',
                    ax=self.axes[0]
                ),
                mpf.make_addplot(
                    self.data['sell_marker'],
                    type='scatter',
                    marker=r'$\mathrm{S}$',
                    markersize=120,
                    color='red',
                    ax=self.axes[0]
                )
            ]

            # 处理空标记
            if self.data['buy_marker'].isna().all():
                self.data.loc[self.data.index[0], 'buy_marker'] = self.data['low'].iloc[0] * 0.98
            if self.data['sell_marker'].isna().all():
                self.data.loc[self.data.index[0], 'sell_marker'] = self.data['high'].iloc[0] * 1.02

            # 重新绘制主K线图和成交量
            mpf.plot(
                self.data,
                type='candle',
                style=style,
                addplot=apds,
                ax=self.axes[0],  # 使用现有的K线轴
                volume=self.axes[2],  # 使用现有的成交量轴
                update_width_config={
                    'candle_linewidth': 0.8,
                    'candle_width': 0.6,
                    'volume_linewidth': 0.8,
                    'volume_width': 0.7
                }
            )

            # 单独绘制指标图（第三个面板）
            self.axes[4].plot(self.fxxs, color='#9467bd')
            self.axes[4].axhline(80, color='#d62728', linestyle='--')
            self.axes[4].axhline(20, color='#2ca02c', linestyle='--')
            self.axes[4].set_ylim(0, 100)
            self.axes[4].set_ylabel('指标')

            # 设置K线图Y轴范围
            self.axes[0].set_ylim(
                self.data['low'].min() * 0.995,
                self.data['high'].max() * 1.005
            )

            # 重新连接悬停事件
            self.connect_hover_event()

            # 刷新画布
            # self.fig.tight_layout()
            self.canvas.draw()

        except Exception as e:
            print(f"图表更新错误: {str(e)}")
            traceback.print_exc()
            # 如果更新失败，回退到完全重建图表
            self.create_professional_chart()



    def add_new_symbol(self):
        """添加新币种"""
        new_symbol = self.new_symbol_var.get().strip().upper()
        if not new_symbol:
            messagebox.showwarning("警告", "请输入有效的币种名称")
            return

        if new_symbol in self.symbol_list:
            messagebox.showinfo("提示", "该币种已在列表中")
            return

        try:
            # 测试币种是否有效
            test_data = get_binance_data(self.limit_var.get(),new_symbol, '1m')
            if len(test_data) == 0:
                raise ValueError("获取不到数据")

            self.symbol_list.append(new_symbol)
            # self.symbol_list = sorted(list(set(self.symbol_list)))
            self.save_symbol_list()
            self.update_symbol_menu()
            self.new_symbol_var.set("")
            messagebox.showinfo("成功", f"已添加币种: {new_symbol}")
        except Exception as e:
            messagebox.showerror("错误", f"无效的交易对: {str(e)}")

    def update_symbol_menu(self):
        """更新币种下拉菜单"""
        menu = self.symbol_menu["menu"]
        menu.delete(0, "end")
        i = 0
        for symbol in self.symbol_list:
            i = i + 1
            n_symbol = str(i) + " " + symbol
            menu.add_command(
                label=n_symbol,
                command=lambda s=n_symbol: self.symbol_var.set(s)
            )

    def change_interval(self, event=None):
        """周期变更处理"""
        self.refresh_data()

    def change_symbol(self, event=None):
        """币种变更处理"""
        self.refresh_data()

    def change_limit(self, event=None):
        """K线数量变更处理"""
        self.refresh_data()

    def connect_hover_event(self):
        """连接鼠标悬停事件"""
        if hasattr(self, 'hover_cid') and self.hover_cid:
            self.canvas.mpl_disconnect(self.hover_cid)

        self.hover = CandleHover(self.fig, self.axes, self.data, self.fxxs)
        self.hover_cid = self.canvas.mpl_connect('motion_notify_event', self.hover)

    def safe_exit(self):
        """安全退出程序"""
        self.running = False

        # 关闭Binance客户端连接
        if hasattr(self, 'client') and self.client:
            try:
                self.client.close_connection()
            except:
                pass

        # 销毁所有资源
        if hasattr(self, 'canvas'):
            self.canvas.get_tk_widget().destroy()
        if hasattr(self, 'fig'):
            plt.close(self.fig)

        # 退出主循环
        self.root.quit()
        self.root.destroy()

        # 强制退出进程
        os._exit(0)

    def monitor_all_symbols(self):
        while self.auto_refresh:
            i = 0
            for symble in self.symbol_list:
                try:
                    print(symble)
                    i = i + 1
                    if not self.auto_refresh:
                        break
                    n_symble = self.symbol_var.get().split(" ")[1]
                    if n_symble == symble or symble in self.pass_symbol:
                        continue
                    data = get_binance_data("100", symble, self.interval_var.get())
                    fxxs, buy = Zhibiao.ths_chaodi(data)
                    ths = float(fxxs[-1])
                    if ths <= 20:
                        util.play_voidce(f"第{i}个{symble}达到买入位置")
                    elif ths >= 80:
                        util.play_voidce(f"第{i}个{symble}达到卖出位置")
                    time.sleep(1)
                except Exception as e:
                    print(e)
                    time.sleep(10)

class CandleHover:
    def __init__(self, fig, axes, data, fxxs):
        self.fig = fig
        self.axes = axes
        self.data = data
        self.fxxs = fxxs
        self.annotation = None
        self.vlines = []
        self.price_label = None
        self.hline = None  # 横线对象
        self.last_close = data['close'].iloc[-1]

        # 存储买卖信号位置
        self.buy_signals = data[data['buy_marker'].notna()].index
        self.sell_signals = data[data['sell_marker'].notna()].index

    def __call__(self, event):
        if not event.inaxes or event.xdata is None:
            self._cleanup()
            return

        try:
            idx = int(round(event.xdata))
            if 0 <= idx < len(self.data):
                candle = self.data.iloc[idx]
                self._update_annotations(idx, candle, event)
                self._update_horizontal_line(event.ydata)  # 更新横线位置
            else:
                self._cleanup()
        except:
            self._cleanup()

        self.fig.canvas.draw_idle()

    def _update_horizontal_line(self, y_value):
        """更新水平线位置"""
        if y_value is None:
            return

        # 获取当前Y轴范围
        y_min, y_max = self.axes[0].get_ylim()

        # 确保y_value在可视范围内
        clamped_y = max(min(y_value, y_max), y_min)

        # 创建或更新横线
        if self.hline is None:
            self.hline = self.axes[0].axhline(
                y=clamped_y,
                color='red',
                linestyle='--',
                alpha=0.7,
                linewidth=1
            )
        else:
            self.hline.set_ydata([y_value, y_value])

        # 计算标签的x位置（稍微向内偏移，避免显示不全）
        x_pos = self.axes[0].get_xlim()[1] * 0.95  # 向右偏移1%

        # 创建或更新价格标签（保留4位小数）
        if self.price_label is None:
            self.price_label = self.axes[0].text(
                x=x_pos,
                y=y_value,
                s=f' {y_value:.6f}',  # 保留4位小数
                color='red',
                va='center',
                ha='left',
                bbox=dict(facecolor='white', alpha=0.7, edgecolor='none')
            )
        else:
            self.price_label.set_position((self.axes[0].get_xlim()[1], y_value))
            self.price_label.set_text(f' {y_value:.6f}')

    def _update_annotations(self, idx, candle, event):
        amplitude = ((candle['high'] - candle['low']) / candle['open']) * 100
        current_change = (candle['close'] - candle['open']) / candle['open'] * 100
        last_change = (self.last_close - candle['close']) / candle['close'] * 100

        self._cleanup()

        # 只在主图和指标图上添加竖线
        # for ax in [self.axes[0], self.axes[1], self.axes[2]]:
        for ax in self.axes:
            if ax:
                vline = ax.axvline(x=idx, color='dodgerblue', alpha=0.7, linestyle='--')
                self.vlines.append(vline)

        # 修复：更精确的信号点判断
        is_buy_signal = not pd.isna(self.data.iloc[idx]['buy_marker'])
        is_sell_signal = not pd.isna(self.data.iloc[idx]['sell_marker'])

        signal_info = ""
        if is_buy_signal:
            prev_sells = self.sell_signals[self.sell_signals < self.data.index[idx]]
            print(prev_sells)
            if len(prev_sells) > 0:
                prev_price = self.data.loc[prev_sells[-1], 'close']
                change_pct = (candle['close'] - prev_price) / prev_price * 100
                signal_info = f"自上次卖出涨幅: {change_pct:+.2f}%"
            else:
                signal_info = "首次买入"

        elif is_sell_signal:
            prev_buys = self.buy_signals[self.buy_signals < self.data.index[idx]]
            if len(prev_buys) > 0:
                prev_price = self.data.loc[prev_buys[-1], 'close']
                change_pct = (candle['close'] - prev_price) / prev_price * 100
                signal_info = f"自上次买入涨幅: {change_pct:+.2f}%"
            else:
                signal_info = "首次卖出"

        text_lines = [
            f"日期: {self.data.index[idx].strftime('%Y-%m-%d %H:%M')}",
            f"收盘: {candle['close']:.6f}",
            f"最高: {candle['high']:.6f}",
            f"最低: {candle['low']:.6f}",
            f"振幅: {amplitude:.2f}%",
            f"涨跌幅: {current_change:+.2f}%",
            f"相对最新价: {last_change:+.2f}%",
            *([signal_info] if signal_info else []),  # 只有signal_info非空时才添加
            f"指标: {self.fxxs[idx]:.2f}"
        ]

        # 使用换行符连接非空行
        text = "\n".join(line for line in text_lines if line)

        x_offset, y_offset = self._smart_position(idx, candle)

        self.annotation = self.axes[0].annotate(
            text,
            xy=(idx, candle['high']),
            xytext=(x_offset, y_offset),
            textcoords='offset points',
            bbox=dict(boxstyle='round', fc='w', ec='0.5', alpha=0.9),
            arrowprops=dict(arrowstyle='->', connectionstyle='arc3')
        )

    def _smart_position(self, idx, candle):
        is_right = idx > len(self.data) * 0.8
        y_range = self.axes[0].get_ylim()
        price_pos = (candle['high'] - y_range[0]) / (y_range[1] - y_range[0])
        is_top = price_pos > 0.7

        if is_right and is_top:
            return -120, -80
        elif is_right:
            return -120, 10
        elif is_top:
            return 15, -80
        else:
            return 15, 10

    def _cleanup(self):
        if self.annotation:
            self.annotation.remove()
            self.annotation = None
        for vline in self.vlines:
            if vline:
                vline.remove()
        self.vlines = []

        # 清理横线
        if self.hline:
            self.hline.remove()
            self.hline = None

        # 清理价格标签
        if self.price_label:
            self.price_label.remove()
            self.price_label = None


if __name__ == "__main__":
    root = tk.Tk()
    app = MplFinanceTkinterApp(root)
    root.protocol("WM_DELETE_WINDOW", app.safe_exit)
    try:
        root.mainloop()
    except KeyboardInterrupt:
        app.safe_exit()
    finally:
        if 'app' in locals():
            app.safe_exit()
        sys.exit(0)
